import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.PriorityQueue;
import java.util.Timer;
import java.util.logging.Logger;

import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.stage.Stage;

public class Functions {

	private static final String NOTHING = "nothing";
	private static final String DISPLAYING = "Displaying\n";
	private static final String LOG_MARKED_AS_DONE = "%1$s marked as done";
	private static final String LOG_ITEM_UPDATED = "Item at %1$s is updated";
	private static final String LOG_NUMBER_OF_REMINDERS_LEFT = "Reminder shown, Number of Reminders left: %1$s";
	private static final String LOG_UNDO_ADD = "Undo ADD";
	private static final String LOG_UNDO_DELETE = "Undo DELETE";
	private static final String LOG_UNDO_DONE = "Undo DONE";
	private static final String LOG_UNDO_UPDATE = "Undo UPDATE";
	private static final String LOG_ITEM_DELETED = "%1$s deleted";
	private static final String LOG_REMINDER_REMOVED_FOR_DELETED_ITEM = "Reminder removed for deleted item";
	private static final String LOG_EVENT_ADDED = "Event added";
	private static final String LOG_DEADLINE_TASK_ADDED = "Deadline task added";
	private static final String LOG_MISC_TASK_ADDED = "Misc task added";
	private static final String LOG_ALL_CHANGES_SAVED_TO_FILE = "All changes saved to File";
	private static final String LOG_LOGGER_FOR_FUNCTIONS = "Logger for Functions";

	private static final String MESSAGE_ALL_DONE_TASK_IS_DELETED = "All Done Task is deleted\n";
	private static final String MESSAGE_NOTHING_TO_UNDO = "Nothing to UNDO\n";
	private static final String MESSAGE_UNDO_SUCCESS = "Last Action Reverted\n";
	private static final String MESSAGE_ADDED = "\"%1$s\" added\n";
	private static final String MESSAGE_CLASHED = "%1$s task(s) clashed, enter Y to add.";
	private static final String MESSAGE_SEARCH_RESULT = "%1$s task(s) found\n";
	private static final String MESSAGE_MULTIPLE_DESCRIPTIONS = "%1$s %2$s. %3$s\n";
	private static final String MESSAGE_ITEM_DELETED = "Item(s) at: \n%1$sdeleted from %2$s\n";
	private static final String MESSAGE_ITEM_MARKED_DONE = "Item(s) at: \n%1$smarked Done %2$s\n";

	private static final String ERROR_INVALID_COMMAND = "ERROR: INVALID COMMAND";
	private static final String ERROR_LARGE_INDEX = "ERROR: INDEX TOO LARGE. LARGEST INDEX IS (%1$s)";
	private static final String ERROR_NEGATIVE_INDEX = "ERROR: INDEX MUST BE POSITIVE. (%1$s)";
	private static final String ERROR_INVALID_LIST = "ERROR: INVALID LIST. (%1$s)";
	private static final String ERROR_END_TIME_BEFORE_START_TIME = "ERROR: END TIME CANNOT BE EARLIER THAN START TIME";
	private static final String ERROR_DEADLINE_ITEMS_CANNOT_HAVE_END_TIME = "ERROR: DEADLINE ITEMS CANNOT HAVE END TIME";
	private static final String ERROR_EVENTS_CANNOT_HAVE_DUE_TIMES = "ERROR: EVENTS CANNOT HAVE DUE TIMES";
	private static final String ERROR_START_TIME_CANNOT_BE_LATER_THAN_END_TIME = "ERROR: START TIME CANNOT BE LATER THAN END TIME";

	private static final String ALL_LOWER = "all";
	private static final String DONE_LOWER = "done";
	private static final String DONE_UPPER = "DONE";
	private static final String SEARCH_UPPER = "SEARCH";
	private static final String SEARCH_UPPER_WITH_SPACE = " SEARCH";
	private static final String CLASH_LOWER = "clash";
	private static final String CLASH_UPPER = "CLASH";
	private static final String TO_DO_UPPER = "TO-DO";
	private static final String TODAY_LOWER = "today";
	private static final String TODAY_UPPER = "TODAY";

	private static final String DONE_MISC_LOWER = "donemisc";
	private static final String DONE_DEADLINE_LOWER = "donedeadline";
	private static final String DONE_EVENT_LOWER = "doneevent";

	private static final String REMINDER_LOWER = "reminder";
	private static final String URGENT_LOWER = "urgent";
	private static final String EVENT_LOWER = "event";
	private static final String DEADLINE_LOWER = "deadline";
	private static final String MISC_LOWER = "misc";

	private static final String EMPTY_STRING = "";
	private static final String NEXT_LINE = "\n";
	private static final String NULL = "null";
	private static final String FALSE = "false";
	private static final String TRUE = "true";
	private static final String ERROR = "Error";
	private static final String SUCCESS = "Success";

	private static final int START_INDEX_OF_DATE = 8;
	private static final int END_INDEX_OF_DATE = 11;
	private static final int START_INDEX_OF_MONTH = 4;
	private static final int START_INDEX_OF_YEAR = 24;
	private static final int END_INDEX_OF_TIME = 16;
	private static final int START_INDEX_OF_TIME = 10;

	private static PriorityQueue<Task> _eventTask;
	private static PriorityQueue<Task> _deadlineTask;
	private static PriorityQueue<Task> _miscTask;
	private static PriorityQueue<Task> _doneEventTask;
	private static PriorityQueue<Task> _doneDeadlineTask;
	private static PriorityQueue<Task> _doneMiscTask;
	private static PriorityQueue<Task> _pq;
	private static PriorityQueue<Task> _searchEventList;
	private static PriorityQueue<Task> _searchDeadlineList;
	private static PriorityQueue<Task> _searchMiscList;
	private static PriorityQueue<Task> _clashList;
	private static PriorityQueue<Task> _reminderQueue;
	private static ArrayList<Task> _reminderList;

	// the following variables are used for UNDO
	private static Task undo_newItem = null;
	private static Task undo_oldItem = null;
	private static int undo_commandCode = -1;
	private static String undo_oldType = null;
	private static String undo_newType = null;

	private static String _listType = null;
	private static ArrayList<Task> _itemsDeletedOrDone;
	private static PriorityQueue<Task> _undo_doneEventTask;
	private static PriorityQueue<Task> _undo_doneDeadlineTask;
	private static PriorityQueue<Task> _undo_doneMiscTask;
	private static Task _clashTask = null;
	private static Logger _logger = Logger.getLogger(LOG_LOGGER_FOR_FUNCTIONS);
	private static String[] _listHeader = new String[3];

	//@author A0099409N
	public Functions() {
		Storage.initialize();
		initializePriorityQueue();
		initializeList();
		setInternalMemory(ALL_LOWER);		
		initializeTimer();		
	}
	
	//For Testing
	public Functions(boolean test) {
	    if (test) {
		Storage.initialize();
		initializePriorityQueue();
		initializeList();
		setInternalMemory(ALL_LOWER);
	    }
	}
	
	//For testing
	public static void deleteAll() {
	    _eventTask.clear();
	    _deadlineTask.clear();
	    _miscTask.clear();
	    _doneEventTask.clear();
	    _doneDeadlineTask.clear();
	    _doneMiscTask.clear();
	}

	private static String formatString(String message, Object... args) {
		return String.format(message, args);
	}

	public void initializePriorityQueue() {
		_eventTask = Storage.getEvent();
		_deadlineTask = Storage.getDeadline();
		_miscTask = Storage.getMisc();
		_doneEventTask = Storage.getDoneEvent();
		_doneDeadlineTask = Storage.getDoneDeadline();
		_doneMiscTask = Storage.getDoneMisc();
		_reminderQueue = Storage.getReminder();
		_searchEventList = new PriorityQueue<Task>();
		_searchDeadlineList = new PriorityQueue<Task>();
		_searchMiscList = new PriorityQueue<Task>();
		_clashList = new PriorityQueue<Task>();
		_undo_doneDeadlineTask = new PriorityQueue<Task>();
		_undo_doneEventTask = new PriorityQueue<Task>();
		_undo_doneMiscTask = new PriorityQueue<Task>();
	}

	private static void initializeList() {
		_itemsDeletedOrDone = new ArrayList<Task>();
		
		_reminderList = new ArrayList<Task>(_reminderQueue);
		Collections.sort(_reminderList, comparator);

	}

	private void initializeTimer() {
		Timer T = new Timer();
		T.schedule(PopUpTimer.T, 1000, 5000);
	}

	private static void setInternalMemory(String s){
		if (s.equals(ALL_LOWER)){
			InternalMemory.setTask(_eventTask, EVENT_LOWER);
			InternalMemory.setTask(_deadlineTask, DEADLINE_LOWER);
			InternalMemory.setTask(_miscTask, MISC_LOWER);
		} else if (s.equals(DONE_LOWER)){
			InternalMemory.setTask(_doneEventTask, EVENT_LOWER);
			InternalMemory.setTask(_doneDeadlineTask, DEADLINE_LOWER);
			InternalMemory.setTask(_doneMiscTask, MISC_LOWER);
		} else if (s.equals(SEARCH_UPPER)){
			InternalMemory.setTask(_searchEventList, EVENT_LOWER);
			InternalMemory.setTask(_searchDeadlineList, DEADLINE_LOWER);
			InternalMemory.setTask(_searchMiscList, MISC_LOWER);
		} else if (s.equals(CLASH_LOWER)){
			InternalMemory.setTask(_clashList, EVENT_LOWER);
		}
	}

	private static int getIndexFromInternalMemory(String type){
		if (type.contains(EVENT_LOWER)){
			return InternalMemory.setTask(_eventTask, EVENT_LOWER);
		} else if (type.contains(DEADLINE_LOWER)){
			return InternalMemory.setTask(_deadlineTask, DEADLINE_LOWER);
		} else if (type.contains(MISC_LOWER)){
			return InternalMemory.setTask(_miscTask, MISC_LOWER);
		}
		return 0;
	}

	private static void saveFile() {
		_reminderQueue.clear();
		_reminderQueue.addAll(_reminderList);
		Storage.updateFile(_eventTask, _deadlineTask, _miscTask, _doneEventTask,
				_doneDeadlineTask, _doneMiscTask, _reminderQueue);

		_logger.info(LOG_ALL_CHANGES_SAVED_TO_FILE);
	}

	private static void addNSortReminder(Task item){
		_reminderList.add(item);
		Collections.sort(_reminderList, comparator);
	}

	public static Feedback addItem(String description, Date start, Date end,
			boolean urgent, boolean reminder, Date reminderTime) {

		TimeSpan timeSpan = new TimeSpan(start, end);
		Reminder reminderFunction = new Reminder(reminder, reminderTime);
		Task item = new Task(description, timeSpan, urgent, reminderFunction);

		if (reminder) {
			addNSortReminder(item);
		}

		int indexOfNewItem = -1;
		item.setJustEdited(true);
		undo_oldItem = item;
		undo_commandCode = 0;

		if (isMiscTask(start, end)) {
			_miscTask.add(item);
			indexOfNewItem = getIndexFromInternalMemory(MISC_LOWER);
			_listType = MISC_LOWER;
			_logger.info(LOG_MISC_TASK_ADDED);
		} else if (isDeadlineTask(end)) {
			_deadlineTask.add(item);
			indexOfNewItem = getIndexFromInternalMemory(DEADLINE_LOWER);
			_listType = DEADLINE_LOWER;
			_logger.info(LOG_DEADLINE_TASK_ADDED);
		} else {
			if (checkIfFree(timeSpan)) {
				return createClashFeedback(item);
			}
			_eventTask.add(item);
			_logger.info(LOG_EVENT_ADDED);
			indexOfNewItem = getIndexFromInternalMemory(EVENT_LOWER);
			_listType = EVENT_LOWER;
		}
		
		changeAllListHeader(TO_DO_UPPER);
		setInternalMemory(ALL_LOWER);
		saveFile();
		String response = formatString(MESSAGE_ADDED,description);
		Feedback feedback = new Feedback(indexOfNewItem, response, _listType, _listHeader);
		return feedback;
	}

	private static Feedback createClashFeedback(Task item) {
		displayClash();
		_clashTask = item;
		_listType = EVENT_LOWER;
		String response = formatString(MESSAGE_CLASHED,_clashList.size());
		Feedback feedback = new Feedback(-1, response, _listType, _listHeader);
		return feedback;
	}

	private static boolean isDeadlineTask(Date end) {
		return end == null;
	}

	private static boolean isMiscTask(Date start, Date end) {
		return start == null && end == null;
	}

	public static Feedback afterClash() {
		Feedback feedback;
		String response;
		_listType = EVENT_LOWER;
		if (_clashTask != null) {
			_eventTask.add(_clashTask);
			changeEventListHeader(TO_DO_UPPER);
			int indexOfNewItem = getIndexFromInternalMemory(EVENT_LOWER);
			response = formatString(MESSAGE_ADDED,_clashTask.getDescription());
			feedback = new Feedback(indexOfNewItem, response, _listType, _listHeader);
		} else {
			feedback = new Feedback(-1, ERROR_INVALID_COMMAND, _listType, _listHeader);
		}
		saveFile();
		return feedback;
	}

	public static void clearClashTask() {
		_clashTask = null;
	}

	private static String performMultiple(ArrayList<Task> tasks, ObservableList<Task> list, 
			String type, ArrayList<Integer> listToDelete) throws Exception{
		String description = EMPTY_STRING;

		for (Integer index : listToDelete) {
			Task item = null;
			if(checkListAndIndex(list, type, index)){
				item = list.get(index-1);
				tasks.add(item);
			}

			description = formatString(MESSAGE_MULTIPLE_DESCRIPTIONS,
					description,index,item.getDescription());
		}
		return description;
	}

	private static boolean checkListAndIndex(ObservableList<Task> list,
			String type, Integer index) throws Exception {
		if (list == null) {
			throw new Exception(formatString(ERROR_INVALID_LIST,type));
		}

		int indexToRemove = index - 1;
		if (indexToRemove < 0) {
			throw new Exception(formatString(ERROR_NEGATIVE_INDEX,index));
		} else if (indexToRemove >= list.size()) {
			throw new Exception(formatString(ERROR_LARGE_INDEX, list.size()));
		} else {
			return true;			
		}
	}

	public static Feedback deleteAll(String type,
			ArrayList<Integer> listToDelete) throws Exception {
		String descriptionToDelete = EMPTY_STRING;
		ArrayList<Task> tasksToDelete = new ArrayList<Task>();
		try {
			ObservableList<Task> list = typeToList(type);
			descriptionToDelete = performMultiple(tasksToDelete,list,type,listToDelete);

			undo_commandCode = 1;
			_itemsDeletedOrDone.clear();
			for (Task t : tasksToDelete) {
				list.remove(t);
				InternalMemory.refreshAllList();
				deleteItem(type, t);
				_itemsDeletedOrDone.add(t);
			}
		} catch (Exception e) {
			throw e;
		}

		String response = formatString(MESSAGE_ITEM_DELETED,descriptionToDelete,_listType);
		return new Feedback(-1, response, type, _listHeader);
	}

	private static void deleteItem(String type, Task item) throws Exception {
		String listStatus = GUIController.getListStatus(type);
		if (listStatus.equals(DONE_UPPER)) {
			_pq = typeToQueue(DONE_LOWER + type);
			_listType = DONE_LOWER + type;
		} else {
			_pq = typeToQueue(type);
			_listType = type;
		}
		_pq.remove(item);
		_logger.info(formatString(LOG_ITEM_DELETED, item.getDescription()));

		if (item.getReminder().getReminderStatus()) {
			if (!_reminderList.isEmpty()) {
				_reminderList.remove(item);
				_logger.info(LOG_REMINDER_REMOVED_FOR_DELETED_ITEM);
			}
		}
		saveFile();
	}

	public static Feedback deleteAllDone() {
		_undo_doneEventTask.addAll(_doneEventTask);
		_doneEventTask.clear();
		_undo_doneDeadlineTask.addAll(_doneDeadlineTask);
		_doneDeadlineTask.clear();
		_undo_doneMiscTask.addAll(_doneMiscTask);
		_doneMiscTask.clear();
		setInternalMemory(DONE_LOWER);
		saveFile();
		undo_commandCode = 4;
		String response = MESSAGE_ALL_DONE_TASK_IS_DELETED;
		Feedback feedback = new Feedback(-1, response, ALL_LOWER, _listHeader);

		return feedback;
	}

	public static Feedback updateItem(String type, int index,
			String description, Date start, Date end, String urgent,
			String reminder, Date reminderTime, boolean isFrom) 
					throws Exception {
		try {
			ObservableList<Task> list = typeToList(type);

			String listType = null;
			boolean isDone = false;

			Task toBeUpdated = null;
			int indexToUpdate = 0;
			if(checkListAndIndex(list, type, index)){
				indexToUpdate = index - 1;
				toBeUpdated = list.get(indexToUpdate);
			}

			String listStatus = GUIController.getListStatus(type);
			if (listStatus.equals(DONE_UPPER)) {
				isDone = true;
				listType = DONE_LOWER + type;
				_pq = typeToQueue(DONE_LOWER + type);
			} else {
				listType = type;
				_pq = typeToQueue(type);
			}
			
			assignUndoVariablesForUpdate(listType, toBeUpdated);

			TimeSpan timeSpan = toBeUpdated.getTimeSpan();

			checkDate(start, end, isFrom, timeSpan);

			list.remove(indexToUpdate);
			_pq.remove(toBeUpdated);

			if (!_reminderList.isEmpty()) {
				_reminderList.remove(toBeUpdated);
			}

			if (!(start == null)) {
				if (timeSpan.isDeadline() && isFrom) {
					timeSpan.setEnd(timeSpan.getStart());
				}
				timeSpan.setStart(start);
			}
			if (!(end == null)) {
				timeSpan.setEnd(end);
			}
			if (!(description == null)) {
				toBeUpdated.setDescription(description);
			}
			if (!(timeSpan == null)) {
				toBeUpdated.setTimeSpan(timeSpan);
			}
			if (!(reminderTime == null)) {
				toBeUpdated.getReminder().setReminderTime(reminderTime);
			}


			updateBoolean(urgent, toBeUpdated, URGENT_LOWER);
			updateBoolean(reminder, toBeUpdated, REMINDER_LOWER);

			toBeUpdated.setJustEdited(true);
			int indexOfUpdated = -1;
			listStatus = TO_DO_UPPER;
			PriorityQueue<Task> pqChanged = new PriorityQueue<Task>();
			PriorityQueue<Task> pqRefreshed1 = new PriorityQueue<Task>();
			PriorityQueue<Task> pqRefreshed2 = new PriorityQueue<Task>();

			if (toBeUpdated.getTimeSpan().isEvent()) {
				if (isDone) {
					pqChanged = typeToQueue(DONE_EVENT_LOWER);
					pqRefreshed1 = typeToQueue(DONE_DEADLINE_LOWER);
					pqRefreshed2 = typeToQueue(DONE_MISC_LOWER);
					listStatus = DONE_UPPER;
					undo_newType = new String(DONE_EVENT_LOWER);
				} else {
					pqChanged = typeToQueue(EVENT_LOWER);
					pqRefreshed1 = typeToQueue(DEADLINE_LOWER);
					pqRefreshed2 = typeToQueue(MISC_LOWER);
					undo_newType = new String(EVENT_LOWER);
				}
				pqChanged.add(toBeUpdated);
				indexOfUpdated = InternalMemory.setTask(pqChanged, EVENT_LOWER);
				InternalMemory.setTask(pqRefreshed1, DEADLINE_LOWER);
				InternalMemory.setTask(pqRefreshed2, MISC_LOWER);

			} else if (toBeUpdated.getTimeSpan().isDeadline()) {
				if (isDone) {
					pqChanged = typeToQueue(DONE_DEADLINE_LOWER);
					pqRefreshed1 = typeToQueue(DONE_EVENT_LOWER);
					pqRefreshed2 = typeToQueue(DONE_MISC_LOWER);
					listStatus = DONE_UPPER;
					undo_newType = new String(DONE_DEADLINE_LOWER);
				} else {
					pqChanged = typeToQueue(DEADLINE_LOWER);
					pqRefreshed1 = typeToQueue(EVENT_LOWER);
					pqRefreshed2 = typeToQueue(MISC_LOWER);
					undo_newType = new String(DEADLINE_LOWER);
				}
				pqChanged.add(toBeUpdated);
				indexOfUpdated = InternalMemory.setTask(pqChanged, DEADLINE_LOWER);
				InternalMemory.setTask(pqRefreshed1, EVENT_LOWER);
				InternalMemory.setTask(pqRefreshed2, MISC_LOWER);

			} else if (toBeUpdated.getTimeSpan().isMisc()) {
				if (isDone) {
					pqChanged = typeToQueue(DONE_MISC_LOWER);
					pqRefreshed1 = typeToQueue(DONE_DEADLINE_LOWER);
					pqRefreshed2 = typeToQueue(DONE_EVENT_LOWER);
					listStatus = DONE_UPPER;
					undo_newType = new String(DONE_MISC_LOWER);
				} else {
					pqChanged = typeToQueue(MISC_LOWER);
					pqRefreshed1 = typeToQueue(DEADLINE_LOWER);
					pqRefreshed2 = typeToQueue(EVENT_LOWER);
					undo_newType = new String(MISC_LOWER);
				}
				pqChanged.add(toBeUpdated);
				indexOfUpdated = InternalMemory.setTask(pqChanged, MISC_LOWER);
				InternalMemory.setTask(pqRefreshed1, DEADLINE_LOWER);
				InternalMemory.setTask(pqRefreshed2, EVENT_LOWER);

			}

			changeAllListHeader(listStatus);
			checkReminder(toBeUpdated);

			undo_newItem = toBeUpdated;
			saveFile();
			_logger.info(formatString(LOG_ITEM_UPDATED,indexOfUpdated));
			String response = formatString(LOG_ITEM_UPDATED,indexOfUpdated) + NEXT_LINE;
			return new Feedback(indexOfUpdated, response, undo_newType, _listHeader);
		} catch (Exception e) {
			throw e;
		}
	}
    
	//@author A0099409N
	public static void assignUndoVariablesForUpdate(String listType,
			Task toBeUpdated) {
		undo_oldItem = new Task(toBeUpdated.getDescription(),
				toBeUpdated.getTimeSpan(), toBeUpdated.getUrgent(),
				toBeUpdated.getReminder());
		undo_commandCode = 2;
		undo_oldType = new String(listType);
	}

	private static void checkReminder(Task toBeUpdated) {
		if (toBeUpdated.getReminder().getReminderStatus()) {
			if (toBeUpdated.getReminder().isReminderValid()) {
				_reminderList.add(toBeUpdated);
				Collections.sort(_reminderList, comparator);
			}
		}
	}

	private static void checkDate(Date start, Date end, boolean isFrom,
			TimeSpan timeSpan) throws Exception {
		if (!(start == null)) {
			if (timeSpan.isEvent()) {
				if (!isFrom) {
					throw new Exception (ERROR_EVENTS_CANNOT_HAVE_DUE_TIMES);
				} 
				if (start.after(timeSpan.getEnd())) {
					throw new Exception (ERROR_START_TIME_CANNOT_BE_LATER_THAN_END_TIME);
				}
			}
			if (timeSpan.isDeadline() && isFrom) {
				if (start.after(timeSpan.getStart())) {
					throw new Exception (ERROR_START_TIME_CANNOT_BE_LATER_THAN_END_TIME);
				}
			}
		}
		if (!(end == null)) {
			if (timeSpan.isEvent()) {
				if (end.before(timeSpan.getStart())){
					throw new Exception (ERROR_END_TIME_BEFORE_START_TIME);
				}
			} else {
				throw new Exception (ERROR_DEADLINE_ITEMS_CANNOT_HAVE_END_TIME);
			}
		}
	}

	private static void updateBoolean(String status, Task item, String function) {
		if (status == null) {
			return;
		}
		if (!status.equals(NULL)) {
			if (status.equals(TRUE)) {
				item.setBoolean(true, function);
			}
			if (status.equals(FALSE)) {
				item.setBoolean(false, function);
			}
		}
	}

	private static PriorityQueue<Task> typeToQueue(String type) {
		if (type.equals(EVENT_LOWER)) {
			return _eventTask;
		} else if (type.equals(DEADLINE_LOWER)) {
			return _deadlineTask;
		} else if (type.equals(MISC_LOWER)) {
			return _miscTask;
		} else if (type.equals(DONE_EVENT_LOWER)) {
			return _doneEventTask;
		} else if (type.equals(DONE_DEADLINE_LOWER)) {
			return _doneDeadlineTask;
		} else if (type.equals(DONE_MISC_LOWER)) {
			return _doneMiscTask;
		}
		return null;
	}

	private static ObservableList<Task> typeToList(String type) {
		if (type.equals(EVENT_LOWER)) {
			return InternalMemory.getEventTask();
		} else if (type.equals(DEADLINE_LOWER)) {
			return InternalMemory.getDeadlineTask();
		} else if (type.equals(MISC_LOWER)) {
			return InternalMemory.getMiscTask();
		}

		return null;
	}

	public static Feedback displayAll() {
		changeAllListHeader(TO_DO_UPPER);
		setInternalMemory(ALL_LOWER);
		Feedback feedback = new Feedback(-1, EMPTY_STRING, ALL_LOWER, _listHeader);
		return feedback;
	}

	public static Feedback displayByType(String type) throws Exception {
		if (type.equals(DONE_LOWER)) {
			changeAllListHeader(DONE_UPPER);
			setInternalMemory(DONE_LOWER);		
		} else if (type.equals(TODAY_LOWER)) {
			Calendar now = Calendar.getInstance();
			PriorityQueue<Task> event = new PriorityQueue<Task>();
			PriorityQueue<Task> deadline = new PriorityQueue<Task>();

			for (Task t : _eventTask) {
				if (isToday(t, now)) {
					event.add(t);
				}
			}
			for (Task t : _deadlineTask) {
				if (isToday(t, now)) {
					deadline.add(t);
				}
			}
			changeEventListHeader(TODAY_UPPER);
			changeDeadlineListHeader(TODAY_UPPER);
			InternalMemory.setTask(event, EVENT_LOWER);
			InternalMemory.setTask(deadline, DEADLINE_LOWER);
		} else {
			throw new Exception(ERROR_INVALID_LIST);
		}
		Feedback feedback = new Feedback(-1, EMPTY_STRING, null, _listHeader);
		return feedback;
	}

	private static boolean isToday(Task t, Calendar now) {
		Date start = t.getTimeSpan().getStart();
		Calendar cal = Calendar.getInstance();
		cal.setTime(start);
		return now.get(Calendar.DAY_OF_MONTH) == cal
				.get(Calendar.DAY_OF_MONTH)
				&& now.get(Calendar.MONTH) == cal.get(Calendar.MONTH)
				&& now.get(Calendar.YEAR) == cal.get(Calendar.YEAR);
	}

	private static void changeAllListHeader(String s) {
		_listHeader[0] = s;
		_listHeader[1] = s;
		_listHeader[2] = s;
	}

	private static void changeListHeaderSeperately(String event, 
			String deadline, String misc){
		_listHeader[0] = event;
		_listHeader[1] = deadline;
		_listHeader[2] = misc;
	}

	private static void changeEventListHeader(String event){
		_listHeader[0] = event;
	}

	private static void changeDeadlineListHeader(String deadline){
		_listHeader[1] = deadline;
	}


	public static Feedback displaySearch() {
		changeAllListHeader(SEARCH_UPPER_WITH_SPACE);
		setInternalMemory(SEARCH_UPPER);
		int itemNo = _searchEventList.size() + _searchDeadlineList.size()
				+ _searchMiscList.size();
		Feedback feedback = new Feedback(-1, formatString(
				MESSAGE_SEARCH_RESULT,itemNo), null, _listHeader);
		return feedback;
	}

	private static void displayClash() {
		changeListHeaderSeperately(CLASH_UPPER,TO_DO_UPPER,TO_DO_UPPER);
		setInternalMemory(CLASH_LOWER);
	}
	
	// @author A0105195J
	public static Feedback doneMultiple(String type,
			ArrayList<Integer> indicesToMarkDone) throws Exception {
		String doneFeedback = EMPTY_STRING;

		ArrayList<Task> tasksToMarkDone = new ArrayList<Task>();
		try {
			ObservableList<Task> list = typeToList(type);
			doneFeedback = performMultiple(tasksToMarkDone, list, type,
					indicesToMarkDone);

			undo_commandCode = 3;
			_listType = type;
			_itemsDeletedOrDone.clear();
			for (Task t : tasksToMarkDone) {
				list.remove(t);
				markDone(type, t);
				InternalMemory.refreshAllList();
				_itemsDeletedOrDone.add(t);
			}
		} catch (Exception e) {
			throw e;
		}

		String response = formatString(MESSAGE_ITEM_MARKED_DONE, doneFeedback,
				_listType);
		return new Feedback(-1, response, type, _listHeader);
	}

	public static void markDone(String type, Task t) throws Exception {

		_pq = typeToQueue(type);
		_pq.remove(t);

		t.setDone(true);
		if (t.getReminder().getReminderStatus()) {
			if (_reminderList.contains(t))
				_reminderList.remove(t);
		}

		PriorityQueue<Task> pq = typeToQueue(DONE_LOWER + type);
		pq.add(t);

		setInternalMemory(ALL_LOWER);
		changeAllListHeader(TO_DO_UPPER);
		
		_logger.info(formatString(LOG_MARKED_AS_DONE, t.getDescription()));
		saveFile();
	}

	// @author A0105195J

	public static Feedback undo() {
		Feedback feedback = new Feedback();
		if (undo_oldItem != null || _itemsDeletedOrDone.size() > 0
				|| undo_commandCode == 4) {

			performUndo();
			resetAllUndoVariables();
			changeAllListHeader(TO_DO_UPPER);
			setInternalMemory(ALL_LOWER);
			saveFile();

			feedback = new Feedback(-1, MESSAGE_UNDO_SUCCESS, SUCCESS,
					_listHeader);

		} else {
			feedback = new Feedback(-1, MESSAGE_NOTHING_TO_UNDO, ERROR,
					_listHeader);
		}

		return feedback;

	}

	// @author A0105195J

	public static void resetAllUndoVariables() {
		undo_oldItem = null;
		undo_commandCode = -1;
		undo_newItem = null;
		_itemsDeletedOrDone.clear();
		_undo_doneDeadlineTask.clear();
		_undo_doneEventTask.clear();
		_undo_doneMiscTask.clear();
	}

	// @author A0105195J

	private static void performUndo() {
		if (undo_commandCode == 0) { // undo ADD
			undoAdd();
			_logger.info(LOG_UNDO_ADD);
		} else if (undo_commandCode == 1) { // undo DELETE
			_pq = typeToQueue(_listType);
			undoDelete(_pq);
			_logger.info(LOG_UNDO_DELETE);
		} else if (undo_commandCode == 2) { // undo UPDATE
			undoUpdate();
			_logger.info(LOG_UNDO_UPDATE);
		}

		else if (undo_commandCode == 3) { // undo DONE
			undoDone();
			_logger.info(LOG_UNDO_DONE);

		} else if (undo_commandCode == 4) {
			undoDoneAll();
		}
	}

	private static void undoDoneAll() {

		_doneDeadlineTask.addAll(_undo_doneDeadlineTask);
		_doneMiscTask.addAll(_undo_doneMiscTask);
		_doneEventTask.addAll(_undo_doneEventTask);

	}

	public static void undoUpdate() {
		_pq = typeToQueue(undo_newType);
		_pq.remove(undo_newItem);
		if (_reminderList.contains(undo_newItem))
			_reminderList.remove(undo_newItem);
		_pq = typeToQueue(undo_oldType);
		_pq.add(undo_oldItem);
		if (undo_oldItem.getReminder().getReminderStatus()
				&& undo_oldItem.getReminder().isReminderValid())
			_reminderList.add(undo_oldItem);
	}

	public static void undoAdd() {
		_pq = typeToQueue(_listType);
		_pq.remove(undo_oldItem);
		if (_reminderList.contains(undo_oldItem))
			_reminderList.remove(undo_oldItem);
	}

	private static void undoDone() {
		for (int i = 0; i < _itemsDeletedOrDone.size(); i++) {
			Task temp = _itemsDeletedOrDone.get(i);
			addToToDoList(temp);
			removeFromDoneList(temp);
			if (temp.getReminder().getReminderStatus()
					&& !_listType.contains(DONE_LOWER)
					&& temp.getReminder().isReminderValid())
				_reminderList.add(temp);
		}

	}

	private static void removeFromDoneList(Task temp) {
		typeToQueue(DONE_LOWER + _listType).remove(temp);
	}

	private static void addToToDoList(Task temp) {
		typeToQueue(_listType).add(temp);
	}

	private static void undoDelete(PriorityQueue<Task> pq2) {
		for (int i = 0; i < _itemsDeletedOrDone.size(); i++) {
			Task temp = _itemsDeletedOrDone.get(i);
			_pq.add(temp);
			if (temp.getReminder().getReminderStatus()
					&& !_listType.contains(DONE_LOWER)
					&& temp.getReminder().isReminderValid()) {
				_reminderList.add(temp);
			}
		}
	}

	//@author A0099409N
	public static Feedback search(String text) {
		_searchEventList.clear();
		_searchDeadlineList.clear();
		_searchMiscList.clear();
		searchDescription(text);
		searchTime(text);
		Feedback feedback = displaySearch();
		return feedback;
	}

	private static void searchDescription(String text) {
		for (Task item : _eventTask) {
			if (isFound(text, item)) {
				_searchEventList.add(item);
			}
		}

		for (Task item : _deadlineTask) {
			if (isFound(text, item)) {
				_searchDeadlineList.add(item);
			}
		}

		for (Task item : _miscTask) {
			if (isFound(text, item)) {
				_searchMiscList.add(item);
			}
		}
	}

	private static void searchTime(String input) {
		for (Task item : _eventTask) {
			if (foundTime(input, item)) {
				_searchEventList.add(item);
			}
		}

		for (Task item : _deadlineTask) {
			if (foundTime(input, item)) {
				_searchDeadlineList.add(item);
			}
		}
	}

	private static boolean foundTime(String input, Task item) {
		boolean status = false;
		String start = formatDateToString(item.getTimeSpan().getStart());
		if (item.getTimeSpan().getEnd() != null) {
			String end = formatDateToString(item.getTimeSpan().getEnd());
			if (end.contains(input.toLowerCase())) {
				status = true;
			}
		}
		if (start.contains(input.toLowerCase())) {
			status = true;
		}
		return status;
	}

	private static boolean isFound(String input, Task item) {
		return item.getDescription().toLowerCase()
				.contains(input.toLowerCase());
	}

	private static String formatDateToString(Date date) {
		String print = date.toString();

		return (dateToString(print) + monthToString(print)
				+ yearToString(print) + timeToString(print)).toLowerCase();
	}

	private static String timeToString(String print) {
		return print.substring(START_INDEX_OF_TIME, END_INDEX_OF_TIME);
	}

	private static String yearToString(String print) {
		return print.substring(START_INDEX_OF_YEAR);
	}

	private static String monthToString(String print) {
		return print.substring(START_INDEX_OF_MONTH, START_INDEX_OF_DATE);
	}

	private static String dateToString(String print){
		return print.substring(START_INDEX_OF_DATE, END_INDEX_OF_DATE);
	}

	private static boolean checkIfFree(TimeSpan timeSpan) {
		_clashList.clear();
		boolean isClash = false;
		for (Task item : _eventTask) {
			if (isOverlapping(timeSpan, item.getTimeSpan())) {
				isClash = true;
				_clashList.add(item);
			}
		}
		return isClash;
	}

	private static boolean isOverlapping(TimeSpan timeSpan1, TimeSpan timeSpan2) {
		return timeSpan1.getStart().before(timeSpan2.getEnd())
				&& timeSpan2.getStart().before(timeSpan1.getEnd());
	}

	public void setListStatus(String event, String deadline, String misc){
		_listHeader[0] = event;
		_listHeader[1] = deadline;
		_listHeader[2] = misc;
	}

	public static void exit() {
		System.exit(0);
	}
	
	// @author A0105195J
	public static Task getTaskWithEarliestReminder() {
		if (_reminderList.size() != 0)
			return _reminderList.get(0);
		return null;
	}

	public static void deleteShownReminder(Task T) {
		assert (_reminderList.size() > 0);
		_reminderList.remove(T);
		_logger.info(formatString(LOG_NUMBER_OF_REMINDERS_LEFT,
				_reminderList.size()));
		saveFile();
	}

	private static Comparator<Task> comparator = new Comparator<Task>() {
		public int compare(Task task1, Task task2) {
			return task1.getReminder().compareTo(task2.getReminder());
		}
	};

	public static Feedback displayCalendarTable(final Calendar time) {
		try {
			Platform.runLater(new Runnable() {
				public void run() {
					try {
						new CalendarTable().start(new Stage());
					} catch (Exception e) {
						e.printStackTrace();
					}

					try {
						CalendarTableController.populateCalendarTable(
								_eventTask, _deadlineTask, time);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new Feedback(-1, DISPLAYING, NOTHING, _listHeader);
	}
}
